﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SCG.General;
using System.IO;
using SCG.SolarVengeanceEngine;
using SCG.WinForms;
using SCG.Prism;
using SCG.SolarVengeance.Brains;

namespace SCG.SolarVengeance
{
    public partial class MainForm : Form, ITerrainImageMapper, ISVGameReconstituter
    {
        //constructor
        public MainForm()
        {
            InitializeComponent();
            _instance = this;
        }

        //singleton instance
        private static MainForm _instance;
        public static MainForm Instance
        {
            get
            {
                return _instance;
            }
        }

        //Star Lord Name
        public string StarLordName { get; set; }

        //Glyph
        public Image Glyph { get; set; }

        //Preferred Color
        public SCGColor PreferredColor { get; set; }

        //The Font Family
        public FontFamily FontFamily
        {
            get
            {
                return _pfc.Families[0];
            }
        }

        //selected ship types
        public List<StarShipType> SelectedShipTypes
        {
            get
            {
                return _selectedShipTypes;
            }
        }

        //settings manager
        public SettingsManager Settings
        {
            get
            {
                return _settings;
            }
        }

        //prism connection
        public PrismConnection Prism
        {
            get
            {
                return prism;
            }
        }

        //game coordinator
        public GameCoordinator GameCoordinator
        {
            get
            {
                return gameCoord;
            }
        }

        //access the title page, since it holds all the dynamically loaded items
        public TitlePage TitlePage
        {
            get
            {
                return _ucTitle;
            }
        }

        //play a sound
        public void PlaySound(string soundName)
        {
            _sounds.PlaySound("Sounds\\" + soundName + ".wav");
        }

        //The game object being played
        public SVGame Game { get; set; }

        //access the confluxer
        public Confluxer Confluxer
        {
            get
            {
                return _names;
            }
        }

        //Change page
        private BasePage _lastPage = null;
        public void ShowPage(Pages page)
        {
            if (_lastPage != null)
                Controls.Remove(_lastPage);
            switch (page)
            {
                case Pages.Title:
                    _lastPage = _ucTitle;
                    break;
                case Pages.Configure:
                    _lastPage = _ucConfigure;
                    break;
                case Pages.SetupSolo:
                    _ucSetup.GameMode = GameMode.Solo;
                    _lastPage = _ucSetup;
                    break;
                case Pages.SetupMulti:
                    _ucSetup.GameMode = GameMode.Multiplayer;
                    _lastPage = _ucSetup;
                    break;
                case Pages.SetupDuel:
                    _ucSetup.GameMode = GameMode.BrainDuel;
                    _lastPage = _ucSetup;
                    break;
                case Pages.Game:
                    _lastPage = _ucGame;
                    break;
                case Pages.Lobby:
                    _lastPage = _ucLobby;
                    break;
                case Pages.BrainDuel:
                    _lastPage = _ucBrainDuel;
                    break;
                case Pages.Help:
                    if (_ucHelp == null)
                        _ucHelp = new HelpPage();
                    _lastPage = _ucHelp;
                    break;
                default:
                    throw new ArgumentException("Invalid Page: " + page);
            }
            _lastPage.Location = new Point(0, 0);
            _lastPage.Dock = DockStyle.Fill;
            Controls.Add(_lastPage);
            _lastPage.Initialize();
        }

        //persist settings
        public void PersistStarLordSettings()
        {
            _settings.Set("Name", StarLordName);
            _settings.Set("Glyph", ImageUtils.ImageToString(Glyph));
            _settings.Set("Color", PreferredColor);
            string shipTypeString = "";
            for (int n = 0; n < SelectedShipTypes.Count; n++)
            {
                string key = "ShipType" + (n + 1);
                _settings.Set(key, (int)SelectedShipTypes[n]);
                shipTypeString += ((int)SelectedShipTypes[n]) + ",";
            }
            _settings.SaveSettings();

            //if we're logged into PrismServer, save selected ship types and color choice in user info
            if (prism.LoggedIn)
            {
                prism.ThisUser.SetDetail("SVShipTypes", shipTypeString);
                prism.ThisUser.SetDetail("SVColor", PreferredColor.ToString());
                prism.SaveUserInfo(prism.ThisUser);
            }
        }

        //start a solo game
        public void KickoffGame()
        {
            Game = new SVGame(_ucGame);
            _ucSetup.Scenario.BuildScenaio(Game, _names, _ucSetup.HumanPlayers + _ucSetup.BrainPlayers, _ucSetup.CapitalValues, _ucSetup.StartingResources, this);
            
            if (_ucSetup.GameMode == GameMode.Solo)
            {
                Game.CurrentPlayer = Game.Players[0];
                Game.CurrentPlayer.Name = StarLordName;
                Game.CurrentPlayer.Glyph = Glyph;
                Game.CurrentPlayer.Color = PreferredColor;
            }
            else if (_ucSetup.GameMode == GameMode.Multiplayer)
            {
                for (int n = 0; n < prism.Users.Count; n++)
                {
                    PrismUser user = prism.Users[n];
                    Player p = Game.Players[n];
                    if (user.UserName == prism.ThisUser.UserName)
                        Game.CurrentPlayer = p;
                    p.Name = user.UserName;
                    p.Glyph = user.Glyph;
                    string colorString = user.GetDetail("SVColor");
                    if (colorString != "")
                        p.Color = new SCGColor(colorString);
                }
            }

            //instantiate Brains
            if (_ucSetup.BrainPlayers > 0)
            {
                List<Brain> brains = _ucSetup.GetBrainList(_ucSetup.BrainPlayers);
                foreach (Player bp in Game.Players)
                {
                    if (bp.Index >= _ucSetup.HumanPlayers)
                    {
                        Brain b = null;
                        if (bp.DesiredBrainName != null && bp.DesiredBrainName != "")
                            b = _ucSetup.GetBrain(bp.DesiredBrainName);
                        if (b == null)
                        {
                            b = brains[0];
                            brains.RemoveAt(0);
                        }
                        bp.Brain = TitlePage.CloneBrain(b);
                    }
                }
            }
            SetBrainGlyphs();

            //available ship types, ensure bonus ship type appears last in list
            if (_ucSetup.GameMode == GameMode.Multiplayer)
            {
                for (int n = 0; n < prism.Users.Count; n++)
                {
                    Player p = Game.Players[n];
                    PrismUser user = prism.Users[n];
                    string colorString = user.GetDetail("SVColor");
                    if (colorString != "")
                        p.Color = new SCGColor(colorString);
                    string shipTypeString = user.GetDetail("SVShipTypes");
                    if (shipTypeString != "")
                    {
                        string[] tokens = shipTypeString.Split(',');
                        List<StarShipType> shipTypes = new List<StarShipType>();
                        foreach(string token in tokens)
                            if (token.Trim() != "")
                            {
                                StarShipType st = (StarShipType)Int32.Parse(token);
                                shipTypes.Add(st);
                            }
                        AssignPlayerShipTypes(p, shipTypes);
                    }
                }
            }
            else if (Game.CurrentPlayer != null)
            {
                AssignPlayerShipTypes(Game.CurrentPlayer, SelectedShipTypes);
            }

            foreach (Player player in Game.Players)
                _ucSetup.Scenario.ProcessStarShipTypes(player.ShipTypesAvailable);

            //ensure colors aren't too similar
            List<string> codes = new List<string>();
            foreach (Player p in Game.Players)
            {
                string code = p.Color.CompareCode;
                while (codes.Contains(code))
                {
                    SCGColor newColor = SCGColor.RandomColor;
                    while (newColor.Luminance < 60)
                        newColor.Brighten();
                    p.Color = newColor;
                    code = newColor.CompareCode;
                }
                if (!codes.Contains(code))
                    codes.Add(code);
            }

            //for multiplayer games, host should send game state to players
            if (_ucSetup.GameMode == GameMode.Multiplayer)
            {
                string s = Game.Persist();
                prism.SendData("GAMESTART|" + s);
            }

            ShowPage(Pages.Game);
            gameCoord.ImpulseMS = _ucSetup.GameMode == GameMode.BrainDuel ? 500 : 1000;
            gameCoord.Start(0);
        }
        private void AssignPlayerShipTypes(Player p, List<StarShipType> shipTypes)
        {
            p.ShipTypesAvailable.Clear();
            p.ShipTypesAvailable.AddRange(shipTypes);
            if (p.Capital != null)
                if (p.Capital.BonusValue > 0)
                    if (!p.ShipTypesAvailable.Contains(p.Capital.BonusShipType))
                        p.ShipTypesAvailable.Add(p.Capital.BonusShipType);
        }

        //kickoff a game after the game state was receicved
        public void KickoffGameForClient(string gameState)
        {
            Game = new SVGame(_ucGame);
            Game.Parse(gameState, this);

            foreach (Player p in Game.Players)
            {
                if (p.Name == prism.ThisUser.UserName)
                    Game.CurrentPlayer = p;
                if (p.IsHuman)
                {
                    PrismUser user = prism.FindUser(p.Name);
                    if (user != null)
                        p.Glyph = user.Glyph;
                }
            }
            SetBrainGlyphs();

            ShowPage(Pages.Game);
            gameCoord.ImpulseMS = 1000;
            gameCoord.Start(0);
        }

        //Kickoff a saved game
        public void KickoffSavedGame(string s)
        {
            try
            {
                Game = new SVGame(_ucGame);
                Game.Parse(s, this);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error reading saved game: " + ex.Message);
                return;
            }
            if (Game.Players[0].IsHuman)
            {
                Game.CurrentPlayer = Game.Players[0];
                Game.CurrentPlayer.Name = StarLordName;
                Game.CurrentPlayer.Glyph = Glyph;
            }
            SetBrainGlyphs();
            ShowPage(Pages.Game);
            gameCoord.ImpulseMS = Game.CurrentPlayer == null ? 500 : 1000;
            gameCoord.Start(Game.Impulse);
        }

        private void SetBrainGlyphs()
        {
            foreach(Player p in Game.Players)
                if (!p.IsHuman)
                {
                    Image glyph = Bitmap.FromFile("Images\\Brains\\" + p.Brain.GetType().Name + ".bmp");
                    glyph = ImageUtils.ResizeBitmap(glyph, 60, 60, true);
                    p.Glyph = glyph;
                }
        }

        //private members
        private SettingsManager _settings;
        private PrivateFontCollection _pfc;
        private List<StarShipType> _selectedShipTypes = new List<StarShipType>();
        private Confluxer _names;
        private SoundManager _sounds = new SoundManager();

        //pages
        private TitlePage _ucTitle;
        private ConfigurationPage _ucConfigure;
        private SetupPage _ucSetup;
        private GamePage _ucGame;
        private LobbyPage _ucLobby;
        private BrainDuelPage _ucBrainDuel;
        private HelpPage _ucHelp;

        //Form load event
        private void MainForm_Load(object sender, EventArgs e)
        {
            //default ship types
            _selectedShipTypes.Add(StarShipType.WarShip);
            _selectedShipTypes.Add(StarShipType.ScanShip);
            _selectedShipTypes.Add(StarShipType.Drone);
            _selectedShipTypes.Add(StarShipType.EMine);
            _selectedShipTypes.Add(StarShipType.SpyShip);
            _selectedShipTypes.Add(StarShipType.Freighter);

            //restore settings
            string fileName = Application.ExecutablePath;
            string appPath = System.IO.Path.GetDirectoryName(fileName) + "\\";
            string dataPath = Application.LocalUserAppDataPath + "\\";
            fileName = dataPath + "Settings.txt";
            _settings = new SettingsManager(fileName);
            int x = _settings.Get("X", Location.X);
            int y = _settings.Get("Y", Location.Y);
            Location = new Point(x, y);
            Width = _settings.Get("Width", Width);
            Height = _settings.Get("Height", Height);

            //load star lord name and glyph
            StarLordName = _settings.Get("Name", "Lord " + Environment.UserName);
            string s = _settings.Get("Glyph", "");
            if (s != "")
                Glyph = ImageUtils.StringToImage(s);
            else
                Glyph = Properties.Resources.StarLord;
            PreferredColor = _settings.Get("Color", SCGColor.Red);

            //persisted ship types
            if (_settings.ContainsKey("ShipType1"))
            {
                SelectedShipTypes.Clear();
                for (int n = 1; n <= 6; n++)
                {
                    string key = "ShipType" + n;
                    StarShipType st = (StarShipType)_settings.Get(key, 0);
                    SelectedShipTypes.Add(st);
                }
            }

            //custom font
            _pfc = new PrivateFontCollection();
            _pfc.AddFontFile("finalnew.ttf");

            //create user interface pages
            _ucTitle = new TitlePage();
            _ucConfigure = new ConfigurationPage();
            _ucSetup = new SetupPage();
            _ucGame = new GamePage();
            _ucLobby = new LobbyPage();
            _ucBrainDuel = new BrainDuelPage();

            //seed names
            string[] seeds = File.ReadAllLines(appPath + "SampleNames.txt");
            _names = new Confluxer(seeds);
            _names.MaxLength = 9;

            //load the title page
            ShowPage(Pages.Title);
        }

        //save settings on form close
        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            _settings.Set("X", Location.X);
            _settings.Set("Y", Location.Y);
            _settings.Set("Width", Width);
            _settings.Set("Height", Height);
            _settings.SaveSettings();
        }

        #region ITerrainImageMapper

        //create terrain of image terrain elements
        public char[,] GetTerrainImageMap(ScenarioImage si, char terrain)
        {
            Bitmap bmp = (Bitmap)si.Image;
            int cellsx = bmp.Width / Constants.CellSize;
            int cellsy = bmp.Height / Constants.CellSize;
            char[,] map = new char[cellsx, cellsy];
            for (int celly = 0; celly < cellsy; celly++)
            {
                for (int cellx = 0; cellx < cellsx; cellx++)
                {
                    for(int y = 0; y < Constants.CellSize; y++)
                        for (int x = 0; x < Constants.CellSize; x++)
                        {
                            Color c = bmp.GetPixel(cellx * Constants.CellSize + x, celly * Constants.CellSize + y);
                            if (c.R > 0 || c.G > 0 || c.B > 0)
                                map[cellx, celly] = terrain;
                            else
                                map[cellx, celly] = ' ';
                        }
                }
            }
            return map;
        }

        //get the image object
        public void AssignImageObject(ScenarioImage si)
        {
            Bitmap bmp = (Bitmap)Bitmap.FromFile(@"Images\Terrain\" + si.ImageFileName + ".bmp");
            int w = bmp.Width / Constants.CellSize * Constants.CellSize;
            int h = bmp.Height / Constants.CellSize * Constants.CellSize;
            bmp = ImageUtils.ResizeBitmap(bmp, w, h, true);
            bmp.MakeTransparent(Color.Black);
            si.Image = ImageUtils.BitmapWithAlpha(bmp, (float)si.Opacity);
        }

        #endregion

        #region GameCoord

        //collect orders
        private void gameCoord_GetOrders(object sender, OrdersEventArgs e)
        {
            //compose all order strings into one string
            Tokenizer tok = new Tokenizer();
            foreach (string s in _ucGame.MyOrders)
                tok.AppendToken(s);
            _ucGame.MyOrders.Clear();
            e.Orders = tok.Result;
        }
        private void gameCoord_ImpulseProcessed(object sender, EventArgs e)
        {
            _ucGame.ImpulseUpdated(gameCoord.Impulse);
        }
        private void gameCoord_ProcessChanges(object sender, MessageEventArgs e)
        {
            Game.ProcessStateChanges(e.Msg);
        }
        private void gameCoord_ProcessOrders(object sender, ProcessOrdersEventArgs e)
        {
            //each item in the list is a list of orders from a different player
            List<string> orders = new List<string>();
            foreach (string playerOrders in e.Orders)
            {
                Tokenizer tok = new Tokenizer(playerOrders);
                while (tok.HasTokens)
                    orders.Add(tok.TokenString);
            }

            //execute brains
            foreach (Player p in Game.Players)
                if (!p.Eliminated)
                    if (p.Brain != null)
                        if (Game.Impulse % 10 == p.Index % 10)
                        {
                            List<string> brainOrders = p.ExecuteBrain();
                            foreach (string brainOrder in brainOrders)
                                orders.Add(brainOrder);
                        }

            e.StateChanges = Game.ProcessImpulse(gameCoord.Impulse, orders);
        }

        #endregion

        //PrismServer event handlers, forward them to appropriate methods in pages

        #region PrismClient Event Handlers

        private void prism_ConnectSuccessful(object sender, EventArgs e)
        {
            _ucLobby.ConnectSuccessful();
        }

        private void prism_ConnectFailed(object sender, MessageEventArgs e)
        {
            _ucLobby.ConnectFailure(e.Msg);
        }

        private void prism_LoginOK(object sender, EventArgs e)
        {
            _ucLobby.LoginSuccessful();

            //request scenarios
            Prism.CustomCommand("SVLogin", "");
        }

        private void prism_LoginError(object sender, MessageEventArgs e)
        {
            _ucLobby.LoginError(e.Msg);
        }

        private void prism_UserAddedToRoom(object sender, PrismUserEventArgs e)
        {
            _ucLobby.UserJoined(e.User);
        }

        private void prism_UserLeftRoom(object sender, PrismUserEventArgs e)
        {
            _ucLobby.UserLeft(e.User);
            if (_lastPage == _ucGame)
            {
                _ucGame.DisplayMessage(e.User.UserName + " has dropped from the game.", true, Color.Yellow);
                Player p = Game.FindPlayer(e.User.UserName);
                if (p != null)
                    p.DroppedFromGame = true;
            }
        }

        private void prism_ChatMessageReceived(object sender, PrismUserMessageEventArgs e)
        {
            _ucLobby.Chat(e.Msg, e.User);
        }

        private void prism_RoomAdded(object sender, RoomNameEventArgs e)
        {
            _ucLobby.RoomAdded(e.RoomName);
        }

        private void prism_RoomRemoved(object sender, RoomNameEventArgs e)
        {
            _ucLobby.RoomRemoved(e.RoomName);
        }

        private void prism_JoinedRoom(object sender, RoomNameEventArgs e)
        {
            _ucLobby.JoinRoom(e.RoomName);
        }

        private void prism_Disconnected(object sender, EventArgs e)
        {
            _ucLobby.Disconnected();
        }

        private void prism_PrismClientError(object sender, MessageEventArgs e)
        {
            MessageBox.Show("Prism Client Error: " + e.Msg);
        }

        private void prism_PrismServerError(object sender, MessageEventArgs e)
        {
            MessageBox.Show("Prism Server Error: " + e.Msg);
        }

        private void prism_AdminMessageReceived(object sender, MessageEventArgs e)
        {
            MessageBox.Show("Message from PrismServer Admin: " + e.Msg);
        }

        private void prism_RoomListReceived(object sender, RoomInfoEventArgs e)
        {
            _ucLobby.ClearRooms();
            foreach (RoomInfo ri in e.Rooms)
            {
                _ucLobby.RoomAdded(ri.RoomName);
                _ucLobby.RoomCountChanged(ri.RoomName, ri.GuestCount);
                if (ri.IsLocked)
                    _ucLobby.LockRoom(ri.RoomName);
            }
        }

        private void prism_RoomCountChanged(object sender, RoomCountEventArgs e)
        {
            _ucLobby.RoomCountChanged(e.RoomName, e.Count);
        }

        private void prism_StartSignal(object sender, EventArgs e)
        {
            if (_ucLobby.HostedGame)
                KickoffGame();
        }

        private void prism_DataMessageReceived(object sender, PrismUserMessageEventArgs e)
        {
            string s = e.Msg;
            if (s.StartsWith("GAMESTART|"))
            {
                s = s.Substring(10);
                KickoffGameForClient(s);
            }
            else if (s.StartsWith("GAMECHAT|"))
            {
                string[] tokens = s.Split('|');
                ChatChannel channel = (ChatChannel)Int32.Parse(tokens[1]);
                Player p = Game.FindPlayer(tokens[2]);
                _ucGame.GameChat(p, channel, tokens[3]);
            }
        }

        #endregion

        #region ISVGameReconstituter

        public Scenario GetScenario(string scenarioName)
        {
            return _ucTitle.FindScenario(scenarioName);
        }

        public Brain GetBrain(string brainName)
        {
            Brain brain = _ucTitle.FindBrain(brainName);
            return _ucTitle.CloneBrain(brain);
        }

        #endregion

        //custom commands
        private void prism_CustomCommandReceived(object sender, CustomCommandEventArgs e)
        {
            switch (e.CommandString)
            {
                case "SVScenarios":
                    {
                        SourceCodeCompiler compiler = new SourceCodeCompiler();
                        string scenString = e.Params;
                        Tokenizer tok = new Tokenizer(scenString);
                        int num = tok.TokenInt;
                        while (num > 0)
                        {
                            string scenName = tok.TokenString;
                            string source = tok.TokenString;
                            Scenario scenario = (Scenario)compiler.CompileSource(source, "Scenario", "SCG.SolarVengeanceEngine;SCG.SolarVengeance.exe");
                            _authorizedScenarios[scenName] = scenario;
                            num--;
                        }
                        _ucLobby.ScenariosReceived();
                    }
                    break;
                case "SVBrainRankings":
                    {
                        List<Brain> ratedBrains = new List<Brain>();
                        string[] lines = e.Params.Split('\r','\n');
                        foreach (string line in lines)
                        {
                            if (line.Trim() == "")
                                continue;
                            string[] tokens = line.Split('|');
                            if (tokens.Length < 2)
                                continue;
                            string brainName = tokens[0];
                            Brain brain = TitlePage.FindBrain(brainName);
                            if (brain != null)
                            {
                                double rating = Double.Parse(tokens[1]);
                                brain.Rating = rating;
                                ratedBrains.Add(brain);
                            }
                        }
                        _ucBrainDuel.BrainRankingsRecieved(ratedBrains);
                    }
                    break;
                case "SVStarLordRankings":
                    {
                        List<PrismUser> users = new List<PrismUser>();
                        Tokenizer tok = new Tokenizer(e.Params);
                        while (tok.HasTokens)
                        {
                            string s = tok.TokenString;
                            PrismUser user = new PrismUser();
                            user.FromString(s);
                            user.Password = "";
                            users.Add(user);
                        }
                        _ucLobby.StarLordRankingsRecieved(users);
                    }
                    break;
            }
        }

        //authorized scenarios
        private Dictionary<string, Scenario> _authorizedScenarios = new Dictionary<string, Scenario>();
        public Scenario GetAuthorizedScenario(string scenName)
        {
            if (_authorizedScenarios.ContainsKey(scenName))
                return _authorizedScenarios[scenName];
            else
                return null;
        }

        //user info (rating) updated
        private void prism_UserInfoChanged(object sender, PrismUserEventArgs e)
        {
            _ucLobby.UpdateUserInfo(e.User);
        }
    }

    //Page types
    public enum Pages { Title, Configure, SetupSolo, SetupMulti, SetupDuel, Game, Lobby, BrainDuel, Rankings, Help };
}
